#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>

typedef struct tail_timer_data{
   ngx_str_t              *path;
   ngx_open_file_info_t   *of;
   ngx_http_request_t     *r; 
   ngx_buf_t              *b;
   size_t                 file_size;
   
}tail_timer_data;


static char *ngx_http_tailfile(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);

static ngx_command_t  ngx_http_tailfile_commands[] = {

    { ngx_string("tailfile"),
      NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS,
      ngx_http_tailfile,
      0,
      0,
      NULL },
      ngx_null_command
};


static ngx_http_module_t  ngx_http_tailfile_module_ctx = {
    NULL,                          /* preconfiguration */
    NULL,                          /* postconfiguration */

    NULL,                          /* create main configuration */
    NULL,                          /* init main configuration */

    NULL,                          /* create server configuration */
    NULL,                          /* merge server configuration */

    NULL,                          /* create location configuration */
    NULL                           /* merge location configuration */
};


ngx_module_t  ngx_http_tailfile_module = {
    NGX_MODULE_V1,
    &ngx_http_tailfile_module_ctx,      /* module context */
    ngx_http_tailfile_commands,         /* module directives */
    NGX_HTTP_MODULE,               /* module type */
    NULL,                          /* init master */
    NULL,                          /* init module */
    NULL,                          /* init process */
    NULL,                          /* init thread */
    NULL,                          /* exit thread */
    NULL,                          /* exit process */
    NULL,                          /* exit master */
    NGX_MODULE_V1_PADDING
};




static void tailfile_timer_handler(ngx_event_t *ev)
{
    tail_timer_data            *data = (tail_timer_data*)ev->data;
    ngx_str_t                  *path = data->path;
    ngx_buf_t                  *b    = data->b;
    ngx_open_file_info_t       *of   = data->of;
    int                        fd    = of->fd;
    ngx_http_request_t         *r    = data->r;

    off_t                      start, len ,file_size = data->file_size;
    ngx_int_t                  res;
    ngx_log_t                  *log;
    ngx_chain_t                out;
    //ngx_pool_cleanup_file_t    clnf;
    
    

    log = r->connection->log;
    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0,
                   "tailfile_timer_handler  path:%V",path);


    of->test_only = 1;
    if(ngx_open_cached_file(NULL, path, of, r->pool) != NGX_OK){

        of->fd = fd;
        ngx_log_error(NGX_LOG_ALERT, log, of->err,
                        "%s \"%V\" failed", of->failed, path);
      
    }else {

        of->fd = fd;
        size_t pos = lseek(of->fd,0,SEEK_END);
        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0,
                          "last end:%d",pos);


        //if(file_size == of->size){
        if(file_size == pos){
            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0,
                 "the same size: \"%l\"",file_size);
        }else{
            //close(fd); 
            /*if (ngx_close_file(of->fd) == NGX_FILE_ERROR) {
                ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
                          ngx_close_file_n " \"%s\" failed", path->data);
            }
            of->fd = ngx_open_file(path->data, NGX_FILE_RDONLY,
                           NGX_FILE_OPEN, 0);
            if(of->fd == -1){
	       ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
       	              "ngx_open_file \"%s\" failed",path->data);
	       return NGX_DECLINED;
            }

            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0,
                     "of->fd:%d err:%s path:%s",of->fd,strerror(errno),path->data);
            */

            /*rc = ngx_open_and_stat_file(path->data, of, r->pool->log);
                       
            if (rc == NGX_OK && !of->is_dir) {
                clnf.fd = of->fd;
                clnf.name = path->data;
                clnf.log = r->pool->log;
            }*/

            start = file_size>of->size?0:file_size;
            len   = file_size>of->size?of->size:(of->size-file_size);
            data->file_size = of->size;
    
            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0,
                     "extends len:%l start:%l  file_newsize:%d of->fd:%d",len,start,of->size,of->fd);
            /*char *buff = ngx_pcalloc(r->pool,len+100);
            if (buff == NULL) {
        	return NGX_HTTP_INTERNAL_SERVER_ERROR;
    	    }

            pos = lseek(of->fd,start-90,SEEK_SET);
            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0,
                        "lseek(%d) %d",of->fd,pos); 

            size_t readn = read(of->fd,buff,len);
            if(readn == -1)
        	return NGX_HTTP_INTERNAL_SERVER_ERROR;
             ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0,
                        "buff(%d) %s",of->fd,buff); 
            //b->start  = buff;
            //b->end = buff+len;*/
            b->file_pos = start;
            b->file_last = start+len;
        
            b->in_file = 1;
            b->last_buf = 0;
            b->flush  = 1;
            b->last_in_chain = 1;
            b->recycled = 1;
        
            b->file->fd = of->fd;
            b->file->name = *path;
            b->file->log = log;
            b->file->directio = of->is_directio;
        
            out.buf = b;
            out.next = NULL;
            
            r->headers_out.content_length_n=ngx_buf_size(out.buf);
            if((res = ngx_http_send_header(r)) >= NGX_HTTP_SPECIAL_RESPONSE) {
                return;
            }
            ngx_http_output_filter(r, &out);
            //ngx_pool_cleanup_file(&clnf);
 
          }
     }

     if (ev->timedout) {
         ngx_time_update();
     }

     ngx_add_timer(ev,1000);
}



static void cleanup_tailfile_timer(void *data)
{
    ngx_event_t               *event = (ngx_event_t*)data;
    tail_timer_data           *timer_data  = (tail_timer_data*)event->data;
    ngx_http_request_t        *r     = timer_data->r; 
    ngx_log_debug1(NGX_LOG_DEBUG_HTTP,r->connection->log,0,"cleanup_tailfile_timer");
    if(event->timer_set){
      ngx_event_del_timer(event);
    }
}


static ngx_int_t
ngx_http_tailfile_handler(ngx_http_request_t *r)
{
    u_char                    *last;
    off_t                      start, len; 
    size_t                     root;
    ngx_int_t                  rc;
    ngx_uint_t                 level;
    ngx_str_t                  *path;
    ngx_log_t                 *log;
    ngx_buf_t                 *b;
    ngx_chain_t                out;
    ngx_open_file_info_t       *of;
    ngx_http_core_loc_conf_t  *clcf;
    tail_timer_data           *timer_data;
    ngx_event_t               *event;
    ngx_http_cleanup_t        *cln;

    if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD))) {
        return NGX_HTTP_NOT_ALLOWED;
    }

    if (r->uri.data[r->uri.len - 1] == '/') {
        return NGX_DECLINED;
    }

    /*rc = ngx_http_discard_request_body(r);

    if (rc != NGX_OK) {
        return rc;
    }*/

    path = ngx_pcalloc(r->pool, sizeof(ngx_str_t));
    if (path == NULL) {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    last = ngx_http_map_uri_to_path(r, path, &root, 0);
    if (last == NULL) {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    log = r->connection->log;

    path->len = last - path->data;

    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0,
                   "http tailfile filename: \"%V\"", path);

    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);

    //ngx_memzero(&of, sizeof(ngx_open_file_info_t));
    of = ngx_pcalloc(r->pool, sizeof(ngx_open_file_info_t));
    if (of == NULL) {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }


    of->read_ahead = clcf->read_ahead;
    of->directio = clcf->directio;
    of->valid = clcf->open_file_cache_valid;
    of->min_uses = clcf->open_file_cache_min_uses;
    of->errors = clcf->open_file_cache_errors;
    of->events = clcf->open_file_cache_events;

    //if (ngx_open_cached_file(clcf->open_file_cache, path, of, r->pool)
    of->test_only = 1;
    if (ngx_open_cached_file(NULL, path, of, r->pool)
        != NGX_OK)
    {
        switch (of->err) {

        case 0:
            return NGX_HTTP_INTERNAL_SERVER_ERROR;

        case NGX_ENOENT:
        case NGX_ENOTDIR:
        case NGX_ENAMETOOLONG:

            level = NGX_LOG_ERR;
            rc = NGX_HTTP_NOT_FOUND;
            break;

        case NGX_EACCES:

            level = NGX_LOG_ERR;
            rc = NGX_HTTP_FORBIDDEN;
            break;

        default:

            level = NGX_LOG_CRIT;
            rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
            break;
        }

        if (rc != NGX_HTTP_NOT_FOUND || clcf->log_not_found) {
            ngx_log_error(level, log, of->err,
                          "%s \"%s\" failed", of->failed, path->data);
        }

        return rc;
    }

    if (!of->is_file) {

        if (ngx_close_file(of->fd) == NGX_FILE_ERROR) {
            ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
                          ngx_close_file_n " \"%s\" failed", path->data);
        }

        return NGX_DECLINED;
    }

    of->fd = ngx_open_file(path->data, NGX_FILE_RDONLY,
                           NGX_FILE_OPEN, 0);
    if(of->fd == -1){
       ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
            "ngx_open_file \"%s\" failed",path->data);
        return NGX_DECLINED;
    }
    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0,
                     "first of->fd:%d err:%s path:%s",of->fd,strerror(errno),path->data);



    log->action = "sending tailfile to client";

    r->root_tested = !r->error_page;
    r->headers_out.status = NGX_HTTP_OK;
    //r->headers_out.content_length_n = len;
    //r->headers_out.last_modified_time = of.mtime;
    ngx_http_clear_last_modified(r);
    //ngx_http_clear_content_length(r);

    // responding to client 
    r->read_event_handler = ngx_http_test_reading;
    r->write_event_handler = ngx_http_request_empty_handler;

    if (ngx_http_set_content_type(r) != NGX_OK) {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }
    //r->allow_ranges = 1;
    //r->main->count++;
    r->keepalive=1;
    r->discard_body = 1;
    rc = ngx_http_send_header(r);

    if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
        return rc;
    }


    b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
    if (b == NULL) {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    b->file = ngx_pcalloc(r->pool, sizeof(ngx_file_t));
    if (b->file == NULL) {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    start = 0;//of->size>1000?of->size-1000:0;
    len = of->size-start;

    b->file_pos = start;
    b->file_last = of->size;

    b->in_file = 1;
    b->last_buf = 0;
    b->flush  = 1;
    b->last_in_chain = 1;
    b->recycled = 1;

    b->file->fd = of->fd;
    b->file->name = *path;
    b->file->log = log;
    b->file->directio = of->is_directio;

    out.buf = b;
    out.next = NULL;

    ngx_http_output_filter(r, &out);

    timer_data = ngx_pcalloc(r->pool, sizeof(tail_timer_data));
    if (timer_data == NULL) {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }
    timer_data->path         = path;
    timer_data->of           = of;
    timer_data->r            = r; 
    timer_data->b            = b; 
    timer_data->file_size    = of->size; 

    event  = ngx_pcalloc(r->pool, sizeof(ngx_event_t));
    if (event == NULL) {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    event->handler = tailfile_timer_handler; 
    event->data    = timer_data; 
    event->log     = r->connection->log;

    ngx_add_timer(event,1000);

    cln = ngx_http_cleanup_add(r, 0);
    if (cln == NULL) {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    cln->handler = cleanup_tailfile_timer;
    cln->data = event;

    return NGX_OK;
}


static char *
ngx_http_tailfile(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    ngx_http_core_loc_conf_t  *clcf;

    clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
    clcf->handler = ngx_http_tailfile_handler;

    return NGX_CONF_OK;
}
